/* 
 * File:   DielectricShader.cpp
 * Author: brady
 * 
 * Created on February 16, 2011, 1:41 AM
 */

#include "DielectricShader.h"

DielectricShader::DielectricShader() {
     DielectricShader(0.5, 0.5, 0.5, 1.0, 1.0, 1.0);
}

DielectricShader::DielectricShader(double r, double g, double b, double sr, double sg, double sb)
{
    Transparency = 0.0;
    rIndex = 1.0;
    Color = Vector3D(r, g, b);
    SpecularColor = Vector3D(sr, sg, sb);
    AmbientCoefficient = 0.2;
    PhongExponent = 25.0;
    MirrorCoefficient = 0.0;
    recursionDepth = 10;
}



DielectricShader::DielectricShader(const DielectricShader& orig)
{

        this->AmbientCoefficient = orig.AmbientCoefficient;
        this->Color = orig.Color;
        this->MirrorCoefficient = orig.MirrorCoefficient;
        this->PhongExponent = orig.PhongExponent;
        this->SpecularColor = orig.SpecularColor;
        this->recursionDepth = orig.recursionDepth;
        this->rIndex = orig.rIndex;
        this->Transparency = orig.Transparency;
        this->texture = orig.texture;

}


DielectricShader::~DielectricShader() {
}


Vector3D DielectricShader::Refract(Vector3D &normal, Vector3D &viewDir, double rOld, double rNew)
{
    Vector3D refracted = Vector3D(0.0, 0.0, 0.0);
    viewDir = viewDir * -1.0;
    double n = (rOld/ rNew);
    double c1 = normal.dot(viewDir) * -1.0;
    double c2 = 1.0 - ((1.0 - (c1 * c1))*(n*n));
    if(c2 < 0.0)
    {
        refracted = Vector3D(0.0, 0.0, 0.0);
        return refracted;
    }
    c2 = sqrt(c2);

    refracted = (viewDir * n) + normal * ((n*c1) - c2);
    refracted.normalize();
    return refracted;
}



Vector3D DielectricShader::ComputeRefractions(Ray &hitRay, Vector3D &normal, Scene* scene)
{
    Vector3D refractedLight(0.0, 0.0, 0.0);
    Vector3D viewDir = hitRay.GetDirection();
    Vector3D point = hitRay.GetOrigin();
    if(Transparency > 0.0)
    {
        Ray refractedRay;
        int rd = hitRay.GetRecursionDepth() - 1;
        if(rd < 0)
        {
            return this->Color;
        }
        double ndotv = normal.dot(viewDir);
        if(ndotv > 0.0) //entering shape
        {
            refractedRay = Ray(point, Refract(normal, viewDir, hitRay.RefractiveIndex.back() , this->rIndex));
            refractedRay.RefractiveIndex = hitRay.RefractiveIndex;
            refractedRay.sNum = hitRay.sNum;
            refractedRay.RefractiveIndex.push_back(this->rIndex);
            refractedRay.SetRecursionDepth(rd);
        }
        else //leaving shape
        {
            Vector3D revNormal = normal*-1.0;
            refractedRay = Ray(point, Refract(revNormal, viewDir, this->rIndex , hitRay.RefractiveIndex.back()));
            refractedRay.SetRecursionDepth(rd);
            refractedRay.RefractiveIndex = hitRay.RefractiveIndex;
            refractedRay.sNum = hitRay.sNum;
            if(refractedRay.RefractiveIndex.size() > 1)
            refractedRay.RefractiveIndex.pop_back();
        }

        if(refractedRay.GetDirection()[0] == 0.0
                && refractedRay.GetDirection()[1] == 0.0
                && refractedRay.GetDirection()[2] == 0.0)
        {
            refractedLight = Vector3D(0.0, 0.0, 0.0);
        }
        HitStruct hs;
        bool hit = scene->BVHRoot->TestIntersect(refractedRay, scene->Tmin, scene->Tmax, hs);
        Shader* refractedShader = hs.shader;
        if(!hit)
        {
            refractedLight = scene->BGColor;
        }
        else
        {
            refractedLight = refractedShader->Shade(refractedRay, hs, scene);
        }
    }
    return refractedLight;
}

Vector3D DielectricShader::Shade(Ray &viewingRay, HitStruct& hitStruct, Scene* scene)
{
    Vector3D shadedLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D highLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D reflectedLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D refractedLight = Vector3D(0.0, 0.0, 0.0);
    Vector3D view = viewingRay.GetDirection() * -1.0;
    view.normalize();
    Vector3D hitpoint = viewingRay.Follow(hitStruct.tVal);
    Ray hitRay(hitpoint, view);
    hitRay.SetRecursionDepth(viewingRay.GetRecursionDepth());
    hitRay.RefractiveIndex = viewingRay.RefractiveIndex;
    hitRay.sNum = viewingRay.sNum;
    Vector3D surfaceNormal = hitStruct.normal;
    //add offset from texture
    Vector3D displacement = this->texture->GetNormalOffsetAt(hitRay.GetOrigin());
    surfaceNormal += displacement;
    surfaceNormal.normalize();
    
    shadedLight = ComputeLighting(hitRay, surfaceNormal, scene);
    highLight = ComputeHighlights(hitRay, surfaceNormal, scene);
    refractedLight = ComputeRefractions(hitRay, surfaceNormal, scene);
    if(Transparency < 1.0)
    reflectedLight = ComputeReflections(hitRay, surfaceNormal, scene);
    
    Vector3D pointColor = this->texture->GetColorAt(hitRay.GetOrigin());
    Vector3D finalColor = Mix(pointColor, shadedLight) * (1-AmbientCoefficient) + (pointColor * AmbientCoefficient);
    //this would be our color without highlights or reflections or transparency
    finalColor = finalColor * (1.0 - Transparency) + refractedLight * Transparency;
    //color with transparency 
    finalColor = finalColor * (1.0 - MirrorCoefficient) + reflectedLight * MirrorCoefficient;
    //now it has color mixed with reflections
    finalColor = finalColor + highLight;
    //and now highlights are added
    finalColor = Cap(finalColor, 1.0);

    return finalColor;
}
